﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Linq;

namespace Xms.Abp.Linq;

[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IQueryableExecuter), typeof(IAsyncQueryableExecuter))]
public class QueryableExecuter : AsyncQueryableExecuter, IQueryableExecuter
{
    protected virtual new IEnumerable<IQueryableExecuterProvider> Providers { get; }

    public QueryableExecuter(IEnumerable<IQueryableExecuterProvider> providers) : base(providers)
    {
        Providers = providers;
    }

    protected virtual new IQueryableExecuterProvider FindProvider<T>(IQueryable<T> queryable)
    {
        return Providers.FirstOrDefault(p => p.CanExecute(queryable));
    }

    public IQueryable<T> AsNoTracking<T>(IQueryable<T> queryable) where T : class
    {
        var provider = FindProvider(queryable);
        return provider != null ? provider.AsNoTracking(queryable) : queryable;
    }

    public IQueryable<T> Include<T, TProperty>(IQueryable<T> queryable, Expression<Func<T, TProperty>> navigationPropertyPath) where T : class
    {
        var provider = FindProvider(queryable);
        return provider != null ? provider.Include(queryable, navigationPropertyPath) : queryable;
    }

    public IQueryable<T> Include<T>(IQueryable<T> queryable, string navigationPropertyPath) where T : class
    {
        var provider = FindProvider(queryable);
        return provider != null ? provider.Include(queryable, navigationPropertyPath) : queryable;
    }

    public IQueryable<T> IncludeIf<T, TProperty>(IQueryable<T> queryable, bool isInclude, Expression<Func<T, TProperty>> navigationPropertyPath) where T : class
    {
        var provider = FindProvider(queryable);
        if (isInclude)
        {
            return provider != null ? provider.Include(queryable, navigationPropertyPath) : queryable;
        }
        return queryable;
    }

    public IQueryable<T> IncludeIf<T>(IQueryable<T> queryable, bool isInclude, string navigationPropertyPath) where T : class
    {
        var provider = FindProvider(queryable);
        if (isInclude)
        {
            return provider != null ? provider.Include(queryable, navigationPropertyPath) : queryable;
        }
        return queryable;
    }

    public IQueryable<T> IgnoreAutoIncludes<T>(IQueryable<T> queryable) where T : class
    {
        var provider = FindProvider(queryable);
        return provider != null ? provider.IgnoreAutoIncludes(queryable) : queryable;
    }

    public IQueryable<T> IgnoreQueryFilters<T>(IQueryable<T> queryable) where T : class
    {
        var provider = FindProvider(queryable);
        return provider != null ? provider.IgnoreQueryFilters(queryable) : queryable;
    }

    public IQueryable<T> TagWith<T>(IQueryable<T> queryable, string tag) where T : class
    {
        var provider = FindProvider(queryable);
        return provider != null ? provider.TagWith(queryable, tag) : queryable;
    }

    public IQueryable<T> TagWithCallSite<T>(IQueryable<T> queryable, [System.Runtime.CompilerServices.CallerFilePath] string filePath = null, [System.Runtime.CompilerServices.CallerLineNumber] int lineNumber = 0) where T : class
    {
        var provider = FindProvider(queryable);
        return provider != null ? provider.TagWithCallSite(queryable, filePath, lineNumber) : queryable;
    }

    public string ToQueryString<T>(IQueryable<T> queryable) where T : class
    {
        var provider = FindProvider(queryable);
        return provider != null ? provider.ToQueryString(queryable) : queryable.ToString();
    }

    public string ToString<T>(IQueryable<T> queryable) where T : class
    {
        var provider = FindProvider(queryable);
        return provider != null ? provider.ToString(queryable) : queryable.ToString();
    }

    public IQueryable<T> ThenInclude<T, TPreviousProperty, TProperty>(IQueryable<T> queryable, Expression<Func<TPreviousProperty, TProperty>> navigationPropertyPath) where T : class
    {
        var provider = FindProvider(queryable);
        return provider != null ? provider.ThenInclude(queryable, navigationPropertyPath) : queryable;
    }

    public IQueryable<T> ThenIncludeIf<T, TPreviousProperty, TProperty>(IQueryable<T> queryable, bool isInclude, Expression<Func<TPreviousProperty, TProperty>> navigationPropertyPath) where T : class
    {
        var provider = FindProvider(queryable);
        if (isInclude)
        {
            return provider != null ? provider.ThenInclude(queryable, navigationPropertyPath) : queryable;
        }
        return queryable;
    }

    public IQueryable<TResult> ProjectTo<T, TResult>(IQueryable<T> queryable)
        where T : class
        where TResult : class, new()
    {
        var provider = FindProvider(queryable);
        return provider != null ? provider.ProjectTo<T, TResult>(queryable) : queryable.Select(r => new TResult());
    }
}
